package cn.tang.tframe.common.utils.crypt;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CryptUtils {
	public static final String AES_CBC_NO_PADDING = "AES/CBC/NoPadding";
	public static final String AES = "AES";
	public static final Charset CHAR_SET_UTF_8 = Charset.forName("UTF-8");
	private static Logger logger = LoggerFactory.getLogger(CryptUtils.class);
	/*
	 * 16进制数字字符集
	 */
	private static String hexString = "0123456789ABCDEF";

	/**
	 * 取字节数组的md5值的十六进制串表示
	 *
	 * @param b
	 * @return
	 */
	public static String getMd5str(byte[] b) {
		try {
			MessageDigest md = MessageDigest.getInstance("MD5");
			return bytetoHexStringLowwer(md.digest(b));
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 取字节数组的md5值的十六进制串表示
	 *
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public static String getMd5str(String str) {
		try {
			return getMd5str(str.getBytes("UTF-8"));
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 取字节数组的md5值的十六进制串表示
	 *
	 * @throws UnsupportedEncodingException
	 */
	public static String getMd5str(Object... strs) {
		StringBuffer sbuf = new StringBuffer();
		if (null != strs) {
			for (Object s : strs) {
				sbuf.append(s != null ? s.toString() : "");
			}
		}
		return getMd5str(sbuf.toString());
	}

	/**
	 * 将字节数组，转换为字符串
	 *
	 * @param data
	 * @return
	 */
	public static String bytetoHexStringUpper(byte[] data) {
		char hexdigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
		return bytetoHexString(data, hexdigits);
	}

	/**
	 * 将字节数组，转换为字符串
	 *
	 * @param data
	 * @return
	 */
	public static String bytetoHexStringLowwer(byte[] data) {
		char hexdigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
		return bytetoHexString(data, hexdigits);
	}

	private static String bytetoHexString(byte[] data, char[] hexdigits) {
		char[] tem = new char[data.length * 2];
		for (int i = 0; i < data.length; i++) {
			byte b = data[i];
			tem[i * 2] = hexdigits[b >>> 4 & 0x0f];
			tem[i * 2 + 1] = hexdigits[b & 0x0f];
		}
		return new String(tem);
	}

	/**
	 * 通过aes加密
	 *
	 * @param data
	 * @throws UnsupportedEncodingException
	 */
	public static byte[] aesEncrypt(byte[] data, String password) {
		try {
			KeyGenerator keyG = KeyGenerator.getInstance("AES");
			SecureRandom instance = SecureRandom.getInstance("SHA1PRNG");
			instance.setSeed(password.getBytes("UTF-8"));
			keyG.init(128, instance);
			SecretKey key = keyG.generateKey();
			byte[] encodeFormat = key.getEncoded();
			SecretKeySpec sks = new SecretKeySpec(encodeFormat, "AES");
			Cipher cipher = Cipher.getInstance("AES");
			cipher.init(Cipher.ENCRYPT_MODE, sks);
			return cipher.doFinal(data);
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(e);
		} catch (NoSuchPaddingException e) {
			throw new RuntimeException(e);
		} catch (InvalidKeyException e) {
			throw new RuntimeException(e);
		} catch (IllegalBlockSizeException e) {
			throw new RuntimeException(e);
		} catch (BadPaddingException e) {
			throw new RuntimeException(e);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 通过aes解密
	 *
	 * @param data
	 * @return
	 */
	public static byte[] aesDecrypt(byte[] data, String password) {
		try {
			KeyGenerator keyG = KeyGenerator.getInstance("AES");
			SecureRandom instance = SecureRandom.getInstance("SHA1PRNG");
			instance.setSeed(password.getBytes("UTF-8"));
			keyG.init(128, instance);
			SecretKey key = keyG.generateKey();
			byte[] encodeFormat = key.getEncoded();
			SecretKeySpec sks = new SecretKeySpec(encodeFormat, "AES");
			Cipher cipher = Cipher.getInstance("AES");
			cipher.init(Cipher.DECRYPT_MODE, sks);
			return cipher.doFinal(data);
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(e);
		} catch (NoSuchPaddingException e) {
			throw new RuntimeException(e);
		} catch (InvalidKeyException e) {
			throw new RuntimeException(e);
		} catch (IllegalBlockSizeException e) {
			throw new RuntimeException(e);
		} catch (BadPaddingException e) {
			throw new RuntimeException(e);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return null;
	}


	public static String sha1Encoder(String str) throws NoSuchAlgorithmException, UnsupportedEncodingException {
		return bytetoHexStringLowwer(messageDigestEncrypt(str.getBytes("UTF-8"), "SHA1"));
	}

	public static String sha1Digest(String str) {
		try {
			return bytetoHexStringLowwer(messageDigestEncrypt(str.getBytes("UTF-8"), "SHA1"));
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return "";
	}

	public static String sha1Digest(String str, String encode) {
		try {
			return bytetoHexStringLowwer(messageDigestEncrypt(str.getBytes(encode), "SHA1"));
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return "";
	}

	public static String sha1Strings(Object... strs) throws NoSuchAlgorithmException, UnsupportedEncodingException {
		StringBuffer sbuf = new StringBuffer();
		if (null != strs) {
			for (Object s : strs) {
				sbuf.append(s != null ? s.toString() : "");
			}
		}
		return sha1Encoder(sbuf.toString());
	}

	public static byte[] messageDigestEncrypt(byte[] data, String key) throws NoSuchAlgorithmException {
		MessageDigest sha = MessageDigest.getInstance(key);
		sha.update(data);
		return sha.digest();
	}

	public static byte[] hmacSha(String key, byte[] datas, String secret) {
		try {
			if (null == key) {
				key = "HmacSHA1";
			}
			// Get an hmac_sha1 key from the raw key bytes
			byte[] keyBytes = key.getBytes();
			SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA1");

			// Get an hmac_sha1 Mac instance and initialize with the signing key
			Mac mac = Mac.getInstance("HmacSHA1");
			mac.init(signingKey);
			// Compute the hmac on input data bytes
			return mac.doFinal(datas);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static byte[] hmacSha256(byte[] datas, String secret) {
		try {
			// Get an hmac_sha1 key from the raw key bytes
			byte[] keyBytes = secret.getBytes();
			SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA256");

			// Get an hmac_sha1 Mac instance and initialize with the signing key
			Mac mac = Mac.getInstance("HmacSHA256");
			mac.init(signingKey);
			// Compute the hmac on input data bytes
			return mac.doFinal(datas);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private static int parse(char c) {
		if (c >= 'a')
			return (c - 'a' + 10) & 0x0f;
		if (c >= 'A')
			return (c - 'A' + 10) & 0x0f;
		return (c - '0') & 0x0f;
	}

	// 从十六进制字符串到字节数组转换
	public static byte[] hexString2Bytes(String hexstr) {
		byte[] b = new byte[hexstr.length() / 2];
		int j = 0;
		for (int i = 0; i < b.length; i++) {
			char c0 = hexstr.charAt(j++);
			char c1 = hexstr.charAt(j++);
			b[i] = (byte) ((parse(c0) << 4) | parse(c1));
		}
		return b;
	}

	public static String aesIvEncode(String dataEncrypt, String key, String iv) throws Exception {
		try {
			Cipher cipher = Cipher.getInstance(AES_CBC_NO_PADDING);

			byte[] dataBytes = dataEncrypt.getBytes(CHAR_SET_UTF_8);

			int plaintextLength = dataBytes.length;
			int blockSize = cipher.getBlockSize();
			if (plaintextLength % blockSize != 0) {
				plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
			}
			byte[] plaintext = new byte[plaintextLength];

			System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);

			SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(CHAR_SET_UTF_8), AES);
			IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes(CHAR_SET_UTF_8));
			cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
			byte[] encrypted = cipher.doFinal(plaintext);
			return bytetoHexStringLowwer(encrypted);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	public static String aesIvDecode(String dataDesEncrypt, String key, String iv) throws Exception {
		try {
			byte[] encrypted1 = hexString2Bytes(dataDesEncrypt);
			Cipher cipher = Cipher.getInstance(AES_CBC_NO_PADDING);
			SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(CHAR_SET_UTF_8), "AES");
			IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes(CHAR_SET_UTF_8));

			cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);

			byte[] original = cipher.doFinal(encrypted1);
			int i = 0;
			int c = original.length;
			while (original[--c] == 0 && c >= 0) {
				i++;
			}
			int ocLength = original.length - i;
			byte[] origCopy = new byte[ocLength];
			System.arraycopy(original, 0, origCopy, 0, ocLength);

			String originalString = new String(origCopy, CHAR_SET_UTF_8);

			return originalString;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/*
	 * 将字符串编码成16进制数字,适用于所有字符（包括中文）
	 */
	public static String encodeHexString(String str) {
		// 根据默认编码获取字节数组
		byte[] bytes = str.getBytes();
		StringBuilder sb = new StringBuilder(bytes.length * 2);
		// 将字节数组中每个字节拆解成2位16进制整数
		for (int i = 0; i < bytes.length; i++) {
			sb.append(hexString.charAt((bytes[i] & 0xf0) >> 4));
			sb.append(hexString.charAt((bytes[i] & 0x0f) >> 0));
		}
		return sb.toString();
	}

	/*
	 * 将16进制数字解码成字符串,适用于所有字符（包括中文）
	 */
	public static String decodeHexString(String bytes) {
		ByteArrayOutputStream baos = new ByteArrayOutputStream(bytes.length() / 2);
		// 将每2位16进制整数组装成一个字节
		for (int i = 0; i < bytes.length(); i += 2)
			baos.write((hexString.indexOf(bytes.charAt(i)) << 4 | hexString.indexOf(bytes.charAt(i + 1))));
		return new String(baos.toByteArray());
	}

	public static String unescapeUnicode(String str) {
		StringBuffer b = new StringBuffer();
		Matcher m = Pattern.compile("\\u([0-9a-fA-F]{4})").matcher(str);
		while (m.find())
			b.append((char) Integer.parseInt(m.group(1), 16));
		return b.toString();
	}

	public static String encodeUnicode(final String gbString) {
		char[] utfBytes = gbString.toCharArray();
		String unicodeBytes = "";
		for (int byteIndex = 0; byteIndex < utfBytes.length; byteIndex++) {
			String hexB = Integer.toHexString(utfBytes[byteIndex]);
			if (hexB.length() <= 2) {
				hexB = "00" + hexB;
			}
			unicodeBytes = unicodeBytes + "\\u" + hexB;
		}
		System.out.println("unicodeBytes is: " + unicodeBytes);
		return unicodeBytes;
	}

	public static String decodeUnicode(final String dataStr) {
		int end = 0;
		final StringBuffer buffer = new StringBuffer();
		int start = dataStr.indexOf("\\u");
		if (start > 0) {
			buffer.append(dataStr.substring(0, start));
		}
		int tail = start + 6;
		while (start > -1) {
			end = dataStr.indexOf("\\u", start + 2);
			String charStr = "";
			if (end == -1) {
				charStr = dataStr.substring(start + 2, dataStr.length());
			} else {
				charStr = dataStr.substring(start + 2, end);
			}
			char letter = (char) Integer.parseInt(charStr, 16); // 16进制parse整形字符串。
			buffer.append(new Character(letter).toString());
			tail = start + 6;
			start = end;
		}
		return buffer.toString();
	}

	public static void main(String[] args) throws NoSuchAlgorithmException, UnsupportedEncodingException, IOException,
			NoSuchProviderException {
		System.out.println(CryptUtils.getMd5str("player" + "login"));
		System.out.println(CryptUtils.getMd5str("player" + "pay"));
		System.out.println(unescapeUnicode("geotable_id:\u53c2\u6570\u5fc5\u9700"));
		System.out.println(decodeUnicode("geotable_id:\u53c2\u6570\u5fc5\u9700"));
	}
}
